/*
 * Copyright (C) 2002-2005 the xine-project
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301
 * USA
 *
 * $Id: info_widgets.c,v 1.19 2006/03/27 22:11:24 dsalt Exp $
 *
 * nice black information display areas
 */

#include "globals.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>

#include <gtk/gtk.h>
#include <gtk/gtkwidget.h>

#include <xine.h>

#include "i18n.h"
#include "defs.h"
#include "info_widgets.h"
#include "utils.h"
#include "playlist.h"
#include "player.h"

GSList *infobars = NULL, *timewidgets = NULL;
pthread_mutex_t widgets_update_lock = PTHREAD_MUTEX_INITIALIZER;

/*
 * Object/type handling code
 */

#define MAX_LINES 3

typedef gboolean (*gxineinfo_cb) (gpointer);

struct gxineinfo_private_s
{
  gxineinfo_cb postinit, update;
  GtkWidget *line[MAX_LINES];
  int data;
};
typedef struct gxineinfo_private_s gxineinfo_private_t;

static GtkWidgetClass *parent_class = NULL;

static GtkType gxineinfo_get_type (void);

#define GTK_GXINE_INFO(obj)		(GTK_CHECK_CAST ((obj), gxineinfo_get_type (), GtkGxineInfo))
#define GTK_GXINE_INFO_CLASS(klass)	(GTK_CHECK_CLASS_CAST ((klass), gxineinfo_get_type (), GtkGxineInfoClass))
#define GTK_IS_GXINE_INFO(obj)		(GTK_CHECK_TYPE (obj, gxineinfo_get_type ()))
#define GTK_IS_GXINE_INFO_CLASS(klass)	(GTK_CHECK_CLASS_TYPE ((klass), gxineinfo_get_type ()))

#define GET_PRIVATE(o) \
  (G_TYPE_INSTANCE_GET_PRIVATE ((o), gxineinfo_get_type (), \
				gxineinfo_private_t))

static gint
expose_cb (GtkWidget * widget, GdkEventExpose * event)
{
  gxineinfo_private_t *priv;
  gtk_paint_flat_box (widget->style, widget->window,
		      GTK_WIDGET_STATE (widget), GTK_SHADOW_NONE,
		      &event->area, widget, gtk_widget_get_name (widget),
		      widget->allocation.x, widget->allocation.y,
		      widget->allocation.width, widget->allocation.height);
  parent_class->expose_event (widget, event);
  if ((priv = GET_PRIVATE (widget))->postinit)
  {
    priv->postinit ((GtkGxineInfo *) widget);
    priv->postinit = NULL;
  }
  return FALSE;
}

static void
realise_cb (GtkWidget * widget)
{
  parent_class->realize (widget);
  gtk_paint_flat_box (widget->style, widget->window,
		      GTK_WIDGET_STATE (widget), GTK_SHADOW_NONE,
		      NULL, widget, gtk_widget_get_name (widget),
		      widget->allocation.x, widget->allocation.y,
		      widget->allocation.width, widget->allocation.height);
}

static void
class_init (GtkGxineInfoClass * klass)
{
  GtkWidgetClass *widget_class = (GtkWidgetClass *) klass;
  widget_class->realize = realise_cb;
  widget_class->expose_event = expose_cb;
  parent_class = gtk_type_class (GTK_TYPE_ALIGNMENT);
  g_type_class_add_private (klass, sizeof (gxineinfo_private_t));
}

static GtkType
gxineinfo_get_type (void)
{
  static const GTypeInfo info = {
    sizeof (GtkGxineInfoClass), NULL, NULL,
    (GClassInitFunc) class_init, NULL, NULL,
    sizeof (GtkGxineInfo), 0, NULL
  };
  static GtkType type = 0;

  if (!type)
  {
    type = g_type_register_static (GTK_TYPE_ALIGNMENT, "GtkGxineInfo",
				   &info, (GTypeFlags) 0);
    xine_config_register_bool
      (xine, "gui.show_time_remaining", 0,
       _("If set, show the length of the remaining part of the stream."),
       NULL, 0, gtk_true, NULL);
  }
  return type;
}

static void
pad_table (GtkWidget *table, int l, int r, int t, int b, int pad)
{
  GtkWidget *tmp = gtk_label_new (NULL);
  gtk_widget_set_size_request (tmp, 2, 2);
  gtk_table_attach (GTK_TABLE(table), tmp, l, r, t, b,
		    (pad & 1) ? (GTK_FILL | GTK_EXPAND) : GTK_FILL,
		    (pad & 2) ? (GTK_FILL | GTK_EXPAND) : GTK_FILL,
		    0, 0);
}

static GtkWidget *
gxineinfo_new (guint height, PangoEllipsizeMode ellipsize, guint width,
	       gxineinfo_cb postinit, gxineinfo_cb update, const char *name,
	       guint buttons)
{
  GtkWidget *self, *table;
  unsigned int i;
  gxineinfo_private_t *priv;
  char *base, *large, *small;

  g_return_val_if_fail (height <= MAX_LINES, NULL);

  base = g_strdup_printf ("gxine_%s", name);
  large = g_strdup_printf ("%s_large", base);
  small = g_strdup_printf ("%s_small", base);

  self = g_object_new (gxineinfo_get_type (), "xalign", 0.5, "yalign", 0.5,
		       "xscale", 1.0, "yscale", 1.0, NULL);
  gtk_widget_set_name (self, base);

  priv = GET_PRIVATE (self);
  priv->postinit = postinit;
  priv->update = update;
  priv->data = 0;

  gtk_widget_push_composite_child ();

  table = gtk_table_new (height + 2, 3, FALSE);
  gtk_widget_set_name (table, base);
  gtk_container_add (GTK_CONTAINER(self), table);

  /* ensure that the whole of the table area is exposed & drawn */
  pad_table (table, 0, 3, 0, 1, 1);
  pad_table (table, 0, 1, 1, height + 1, 0);
  pad_table (table, 2, 3, 1, height + 1, 0);
  pad_table (table, 0, 3, height + 1, height + 2, 3);

  for (i = 0; i < height; ++i)
  {
    GtkWidget *ev = (buttons & 1 << i) ? gtk_event_box_new () : NULL;
    GtkWidget *line = priv->line[i] = gtk_label_new (NULL);
    gtk_widget_set_name (line, i ? small : large);
    gtk_misc_set_alignment (GTK_MISC (line), 0, 0.5);
    gtk_label_set_ellipsize (GTK_LABEL (line), ellipsize);
    if (!i && width)
      gtk_label_set_width_chars (GTK_LABEL (line), width);
    if (ev)
      gtk_container_add (GTK_CONTAINER (ev), line);
    gtk_table_attach (GTK_TABLE (table), ev ? : line, 1, 2, i + 1, i + 2,
		      GTK_FILL | GTK_EXPAND, GTK_FILL, 0, 0);
  }
  for (; i < MAX_LINES; ++i)
    priv->line[i] = NULL;

  gtk_widget_pop_composite_child ();

  free (base);
  free (large);
  free (small);

  return self;
}

/*
 * Utility functions
 */

void
gxineinfo_update_line (GSList *objs, guint lineno, const char *format, ...)
{
  gchar *text = NULL;

  if (!objs || lineno >= MAX_LINES)
    return;

  if (format)
  {
    va_list argp;
    va_start (argp, format);
    text = g_strdup_vprintf (format, argp);
    va_end (argp);
  }

  foreach_glist (objs, objs)
  {
    gxineinfo_private_t *priv = GET_PRIVATE (objs->data);
    if (priv->line[lineno])
      gtk_label_set_text (GTK_LABEL (priv->line[lineno]), text);
  }
  free (text);
}

void
gxineinfo_update (GSList *objs)
{
  foreach_glist (objs, objs)
    if (GTK_IS_GXINE_INFO (objs->data))
    {
      gxineinfo_private_t *priv = GET_PRIVATE (objs->data);
      if (priv->update)
        priv->update (objs->data);
    }
}

void
gxineinfo_clear (GSList *objs)
{
  int i;
  for (i = 0; i < MAX_LINES; ++i)
    gxineinfo_update_line (objs, i, NULL);
}

/*
 * Info bar
 */

void
infobar_show_metadata (GSList * bars)
{
  char *strbuf = NULL; /* for the main window's title bar */
  const char *t;
  const char *str;
  int n, w, h;
  gboolean show_meta = TRUE;

  if (!bars)
    return;

  if ((t = xine_get_meta_info (stream, XINE_META_INFO_TITLE)))
  {
    const char *a;
    if ((a = xine_get_meta_info (stream, XINE_META_INFO_ARTIST)))
    {
      strbuf = g_strdup_printf ("%s (%s) - gxine %s", t, a, VERSION);
      gxineinfo_update_line (bars, 0, "%s (%s)", t, a);
    }
    else
    {
      strbuf = g_strdup_printf ("%s - gxine %s", t, VERSION);
      gxineinfo_update_line (bars, 0, "%s", t);
    }
  }
  else
  {
    const char *str = NULL;
    if (!playlist_showing_logo ())
    {
      str = player_get_cur_title ();
      if (!str || !str[0])
      {
	const char *mrl = player_get_cur_mrl () ? : "";
	str = (strrchr (mrl, '/') ? : mrl - 1) + 1;	/* leaf */
      }
    }
    if (str && str[0])
    {
      strbuf = g_strdup_printf ("%s - gxine %s", str, VERSION);
      gxineinfo_update_line (bars, 0, "%s", str);
    }
    else
    {
      strbuf = g_strdup_printf ("gxine %s", VERSION);
      show_meta = FALSE;
      gxineinfo_update_line (bars, 0, "%s", strbuf);
    }
  }
  gtk_window_set_title (GTK_WINDOW (app), strbuf);

  /*
   * display some metainfo
   */

  strbuf[0] = 0;
  if (show_meta)
  {
    str = xine_get_meta_info (stream, XINE_META_INFO_SYSTEMLAYER);
    if (str)
      asreprintf (&strbuf, "%s%s ", strbuf, str);

    w = xine_get_stream_info (stream, XINE_STREAM_INFO_VIDEO_WIDTH);
    h = xine_get_stream_info (stream, XINE_STREAM_INFO_VIDEO_HEIGHT);
    if (w && h)
      asreprintf (&strbuf, "%s%d×%d ", strbuf, w, h);

    str = xine_get_meta_info (stream, XINE_META_INFO_VIDEOCODEC);
    if (str)
      asreprintf (&strbuf, "%s%s ", strbuf, str);

    n = xine_get_stream_info (stream, XINE_STREAM_INFO_VIDEO_BITRATE) / 1000;
    if (n > 10 && n < 20000)
      asreprintf (&strbuf, _("%s%d kBit/s "), strbuf, n);
  }
  gxineinfo_update_line (bars, 1, "%s", strbuf);

  strbuf[0] = 0;
  if (show_meta)
  {
    n = xine_get_stream_info (stream, XINE_STREAM_INFO_AUDIO_SAMPLERATE) / 1000;
    if ((n > 0) && (n < 256))
      asreprintf (&strbuf, _("%s%d kHz "), strbuf, n);

    n = xine_get_stream_info (stream, XINE_STREAM_INFO_AUDIO_BITRATE) / 1000;
    if ((n > 0) && (n < 1024))
      asreprintf (&strbuf, _("%s%d kBit/s "), strbuf, n);

    str = xine_get_meta_info (stream, XINE_META_INFO_AUDIOCODEC);
    if (str)
      asreprintf (&strbuf, "%s%s ", strbuf, str);
  }
  gxineinfo_update_line (bars, 2, "%s", strbuf);

  free (strbuf);
}

GtkWidget *
create_infobar (gboolean small)
{
  return gxineinfo_new (small ? 1 : 3, PANGO_ELLIPSIZE_END, 0, NULL, NULL, "info", 0);
}

/*
 * Time display area
 */

static gboolean
update_time_widget_cb (gpointer data)
{
  GtkGxineInfo *widget = data;
  gint pos_stream, pos_time, length_time;
  gxineinfo_private_t *priv = GET_PRIVATE (widget);
  union {
    struct { gchar current[64], pad[4], length[64]; };
    gchar full[132];
  } time;

  pthread_mutex_lock (&widgets_update_lock);
  gdk_threads_enter ();

  time.length[0] = time.current[0] = 0;

  xine_cfg_entry_t entry;
  gboolean state = priv->data ^
		   !!(xine_config_lookup_entry
			(xine, "gui.show_time_remaining", &entry)
		      && entry.num_value);
  gtk_widget_set_state (widget, state ? GTK_STATE_ACTIVE : GTK_STATE_NORMAL);

  if (xine_get_status (stream) == XINE_STATUS_PLAY && !playlist_showing_logo ())
  {

    if (!xine_get_pos_length (stream, &pos_stream, &pos_time, &length_time))
      pos_stream = pos_time = length_time = 0;

    if (state)
      pos_time = length_time - pos_time;

    pos_time -= pos_time % 1000;
    int_to_timestring (pos_time, time.current, sizeof (time.current));

    length_time = round_second (length_time);
    if (length_time > 0)
      int_to_timestring (length_time, time.length, sizeof (time.length));
    else
      snprintf (time.length, sizeof (time.length), "%s",
		xine_get_stream_info (stream, XINE_STREAM_INFO_SEEKABLE)
		  ? _("??:??:??") : _("live"));
  }
  else
    strcpy (time.current, "•");
  if (priv->line[1])
  {
    gtk_label_set_text (GTK_LABEL (priv->line[0]), time.current);
    gtk_label_set_text (GTK_LABEL (priv->line[1]), time.length);
  }
  else if (time.current[0])
  {
    strcat (time.full, " / ");
    memmove (time.full + strlen (time.full), time.length, sizeof (time.length));
    gtk_label_set_text (GTK_LABEL (priv->line[0]), time.full);
  }
  else
    gtk_label_set_text (GTK_LABEL (priv->line[0]), "");

#ifdef XINE_STREAM_INFO_DVD_TITLE_NUMBER
  time.full[0] = 0;
  int count = xine_get_stream_info (stream, XINE_STREAM_INFO_DVD_TITLE_COUNT);
  if (count) /* "T" = DVD title number */
    sprintf (time.full, _("T%d/%d "),
	     xine_get_stream_info (stream, XINE_STREAM_INFO_DVD_TITLE_NUMBER),
	     count);
  count = xine_get_stream_info (stream, XINE_STREAM_INFO_DVD_CHAPTER_COUNT);
  if (count) /* "C" = DVD chapter number */
    sprintf (time.full + strlen (time.full), _("C%d/%d "),
	     xine_get_stream_info (stream, XINE_STREAM_INFO_DVD_CHAPTER_NUMBER),
	     count);
  gtk_label_set_text (GTK_LABEL (priv->line[2]), time.full);
#endif

  gdk_threads_leave ();
  pthread_mutex_unlock (&widgets_update_lock);
  return TRUE;
}

static gboolean
frob_format_cb (GtkWidget *widget, GdkEventButton *event, gpointer data)
{
  if (event->button == 1)
  {
    GET_PRIVATE (data)->data ^= 1;
    update_time_widget_cb (data);
  }
  return TRUE;
}

static gboolean
postinit_time_widget (gpointer widget)
{
  gxineinfo_private_t *priv = GET_PRIVATE (widget);
  g_signal_connect (G_OBJECT (priv->line[0]->parent), "button-press-event",
		    G_CALLBACK (frob_format_cb), widget);

  GdkWindow *window = priv->line[0]->window;
  GdkDisplay *display = gdk_drawable_get_display ((GdkDrawable *)window);
  GdkCursor *ptr = gdk_cursor_new_for_display (display, GDK_CLOCK);
  gdk_window_set_cursor (window, ptr);
  gdk_cursor_unref (ptr);

  g_timeout_add (500, update_time_widget_cb, widget);
  return FALSE;
}

GtkWidget *
create_time_widget (gboolean small)
{
  return gxineinfo_new (small ? 1 : 3, PANGO_ELLIPSIZE_NONE, small ? 16 : 8,
			postinit_time_widget, update_time_widget_cb, "time",
			1 /* line 0 has an event box */);
}
